package jp.wug;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.servlet.RequestScoped;
import jp.wug.util.QueryParameters;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@RequestScoped
public class WugRequest {
    private final WugSession wugSession;
    private WugConversation wugConversation;
    private final HttpServletRequest request;
    private Map<String, String[]> queryParameters = new HashMap<String, String[]>();
    private Map<String, String[]> postParameters = new HashMap<String, String[]>();
    private Class<?> modelType;
    private static Pattern WWW_FORM_URLENCODED_PATTERN = Pattern.compile("([^&=]*)=([^&=]*)");

    @Inject
    public WugRequest(WugSession wugSession, HttpServletRequest request) {
        this.wugSession = wugSession;
        this.request = request;
    }

    public void prepare(Class<?> modelType) throws IOException {
        this.modelType = modelType;
        queryParameters = parseQuery(request.getQueryString());
        if (request.getMethod().equals("POST")) {
            if (!request.getContentType().equals("application/x-www-form-urlencoded")) {
                throw new RuntimeException("ContentType for POST request must be 'application/x-www-form-urlencoded'");
            }
            StringBuffer buffer = readFully(request.getReader());
            postParameters = parseQuery(buffer);
        }

        String[] conversationKeys = getQueryParameter("_conversation");
        if (conversationKeys.length == 1) {
            String key = conversationKeys[0];
            wugConversation = wugSession.getConversation(key);
        }
    }

    public WugSession getSession() {
        return wugSession;
    }

    private StringBuffer readFully(Reader reader) throws IOException {
        StringWriter writer = new StringWriter();
        char[] buffer = new char[1024];
        int n;
        while ((n = reader.read(buffer)) != -1) {
            writer.write(buffer, 0, n);
        }
        writer.flush();
        return writer.getBuffer();
    }

    private Map<String, String[]> parseQuery(CharSequence queryString) throws UnsupportedEncodingException {
        String encoding = request.getCharacterEncoding();
        if (encoding == null) {
            encoding = "UTF-8";
        }
        Map<String, List<String>> map = new HashMap<String, List<String>>();
        if (queryString != null) {
            Matcher matcher = WWW_FORM_URLENCODED_PATTERN.matcher(queryString);
            while (matcher.find()) {
                String name = URLDecoder.decode(matcher.group(1), encoding);
                String value = URLDecoder.decode(matcher.group(2), encoding);
                List<String> list = map.get(name);
                if (list == null) {
                    list = new ArrayList<String>();
                    map.put(name, list);
                }
                list.add(value);
            }
        }
        Map<String, String[]> result = new HashMap<String, String[]>();
        for (Map.Entry<String, List<String>> entry : map.entrySet()) {
            List<String> value = entry.getValue();
            result.put(entry.getKey(), value.toArray(new String[value.size()]));
        }
        return result;
    }

    public String[] getQueryParameter(String name) {
        if (queryParameters.containsKey(name)) {
            return queryParameters.get(name);
        }
        return new String[0];
    }

    public String getPost() {
        String[] strings = postParameters.get("_post");
        if (strings.length != 1) {
            throw new RuntimeException("Bad post request");
        }
        return strings[0];
    }

    public Map<String, String[]> getPostParameters(String name) {
        Map<String, String[]> map = new HashMap<String, String[]>();
        for (Map.Entry<String, String[]> entry : postParameters.entrySet()) {
            if (entry.getKey().equals(name) || entry.getKey().startsWith(name + ":")) {
                map.put(entry.getKey(), entry.getValue());
            }
        }
        return map;
    }

    public String getContextPathPlusServletPath() {
        return request.getContextPath() + request.getServletPath();
    }

    public String getPathInfo() {
        return request.getPathInfo();
    }

    public WugConversation getConversation() {
        if (wugConversation == null) {
            String key = wugSession.startConversation();
            wugConversation = wugSession.getConversation(key);
        }
        return wugConversation;
    }

    public void appendURLParameters(QueryParameters queryParameter) {
        if (wugConversation != null) {
            queryParameter.append("_conversation", wugConversation.key);
        }
    }

    public ResourceBundle getResourceBundle() {
        if (modelType == null) {
            throw new IllegalStateException("WugRequest not prepared.");
        }
        try {
            return ResourceBundle.getBundle("resourcebundle", getSession().getLocale(), modelType.getClassLoader());
        } catch (MissingResourceException e) {
            return new ResourceBundle() {
                protected Object handleGetObject(String key) {
                    return null;
                }

                public Enumeration<String> getKeys() {
                    return null;
                }
            };
        }
    }

    public Provider<ResourceBundle> getResourceBundleProvider() {
        return new Provider<ResourceBundle>() {
            public ResourceBundle get() {
                return getResourceBundle();
            }
        };
    }

    public Class<?> getModelClass() {
        return modelType;
    }
}